Flutter SingleViewPresentation

在 Flutter 嵌原生视图能力的 Flutter Virtual displays 模式下,SingleViewPresentation 是 Android VirtualDisplay 的视图容器,平台视图既是在 SingleViewPresentation 内创建,也是由 SingleViewPresentation 触发创建。在本文中,将梳理 SingleViewPresentation 内部逻辑。对于 SingleViewPresentation 是如何被创建、使用的,参见《Flutter VirtualDisplayController》。

SingleViewPresentation 覆写了 WindowManager 的 addView/removeView/updateViewLayout 方法,使得向 WindowManager 中添加的视图,会被添加到 SingleViewPresentation 下。SingleViewPresentation 内部的视图结构如下:

           rootView
          /         \
         /           \
        /             \
    container       state.fakeWindowViewGroup
       |
    EmbeddedView

Presentation 与 Activity

Presentation 这个概念我之前了解较少,在 Android VirtualDisplay 下,使用 Presentation 进行视图展示。我将 Presentation 与 Activity 进行对比:

概念 Activity Presentation
用途 Android 四大组件,表示整屏视窗 特殊的对话框,用于在第二屏幕上显示用户界面
生命周期 复杂,包括创建、启动、恢复、暂停、停止和销毁等状态 简单,只有创建、显示、隐藏

总之,都是有生命周期的视图容器


核心成员

SingleViewPresentation 类的成员属性属下:

// 原生视图工厂
private final PlatformViewFactory viewFactory;

// 处理和分派无障碍事件
private final AccessibilityEventsDelegate accessibilityEventsDelegate;

// 监听焦点变化
private final OnFocusChangeListener focusChangeListener;

// 表示由Flutter框架分配给嵌入视图的视图ID。
private int viewId;

// 创建平台视图时的参数。
private Object createParams;

// Presentation 根视图
private AccessibilityDelegatingFrameLayout rootView;

private FrameLayout container;

// 存储与 SingleViewPresentation实例相关的状态信息
private final PresentationState state;

// 是否开始聚焦。
private boolean startFocused = false;

// 表示托管`FlutterView`的应用程序窗口的 Context
private final Context outerContext;

其中,比较重要的:


PresentationState

在类属性中,有一个类型为 PresentationState 内部类的属性 state。在 Flutter Virtual displays 模式下,一旦 AndroidView 大小变化,Android VirtualDisplay 就会销毁重建,而依附于 Android VirtualDisplay 的 Presentation 也会重新创建,那原生视图怎么办?这就是 PresentationState 存在的意义,将在 Presentation 切换过程中,需要保留的状态封装起来(包括原生视图),在切换 Presentation 时,将 PresentationState 实例传递过去。

以下是对代码注释的解释:当在 Flutter 应用中嵌入的 Android 视图需要调整大小时,我们会将这个视图移动到一个新的虚拟显示设备上,这个新的虚拟显示设备具有新的大小。PresentationState 类用于存储与视图一起移动到新的虚拟显示设备的演示状态。

该类代码如下:

static class PresentationState {
  // 在 Flutter 应用中嵌入的 Android 原生视图
  private PlatformView platformView;

  // 它是一个 InvocationHandler,用于代理 WindowManager。
  // 这基本上是 presentation 的自定义 WindowManager。
  private WindowManagerHandler windowManagerHandler;

  // 它包含直接添加到窗口管理器的视图(例如`android.widget.PopupWindow`)。
  private FakeWindowViewGroup fakeWindowViewGroup;
}

onCreate 方法

onCreate 方法是 Presentation 的初始化方法,用于创建视图布局:

@Override
protected void onCreate(Bundle savedInstanceState) {
  super.onCreate(savedInstanceState);
  // ...

  // 它包含直接添加到窗口管理器的视图
  if (state.fakeWindowViewGroup == null) {
    state.fakeWindowViewGroup = new FakeWindowViewGroup(getContext());
  }

  // 它是一个 InvocationHandler,用于代理 WindowManager。
  // 这基本上是 presentation 的自定义 WindowManager。
  if (state.windowManagerHandler == null) {
    WindowManager windowManagerDelegate =
        (WindowManager) getContext().getSystemService(WINDOW_SERVICE);
    state.windowManagerHandler =
        new WindowManagerHandler(windowManagerDelegate, state.fakeWindowViewGroup);
  }

  // 这是一个 FrameLayout 对象,当平台视图附加到演示时,它包含嵌入的平台视图
  container = new FrameLayout(getContext());

  // 替换系统的窗口管理器
  Context context =
      new PresentationContext(getContext(), state.windowManagerHandler, outerContext);

  // 创建平台视图
  if (state.platformView == null) {
    state.platformView = viewFactory.create(context, viewId, createParams);
  }

  View embeddedView = state.platformView.getView();
  container.addView(embeddedView);
  rootView =
      new AccessibilityDelegatingFrameLayout(
          getContext(), accessibilityEventsDelegate, embeddedView);
  rootView.addView(container);
  rootView.addView(state.fakeWindowViewGroup);

  embeddedView.setOnFocusChangeListener(focusChangeListener);
  rootView.setFocusableInTouchMode(true);
  // 启动时是否需要焦点
  if (startFocused) {
    embeddedView.requestFocus();
  } else {
    rootView.requestFocus();
  }
  setContentView(rootView);
}

其中:


构造函数

SingleViewPresentation 有两个构造函数,这里看其中之一:

public SingleViewPresentation(
    Context outerContext,
    Display display,
    PlatformViewFactory viewFactory,
    AccessibilityEventsDelegate accessibilityEventsDelegate,
    int viewId,
    Object createParams,
    OnFocusChangeListener focusChangeListener) {
  // 初始化父类,并传入定制 ImmContext
  super(new ImmContext(outerContext), display);
  this.viewFactory = viewFactory;
  this.accessibilityEventsDelegate = accessibilityEventsDelegate;
  this.viewId = viewId;
  this.createParams = createParams;
  this.focusChangeListener = focusChangeListener;
  this.outerContext = outerContext;
  // 创建 state
  state = new PresentationState();
  getWindow()
      .setFlags(
          WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE,
          WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE);
  if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) {
    getWindow().setType(WindowManager.LayoutParams.TYPE_PRIVATE_PRESENTATION);
  }
}

其中,需要注意的是,在初始化父类时,传入了一个定制的 ImmContext,其中 Imm 表示输入法,表示其中有输入法相关定制逻辑。


ImmContext

注释含义:ImmContext在构造时会缓存InputMethodManager(IMM)的实例,并且不会获取任何后续的更改。在极少数情况下,如果Flutter视图更改了窗口,这将返回一个过时的实例。这个问题应该被修复,而不是延迟返回IMM,应该返回知道Flutter视图真实上下文的对象。

Error

这里我联想到的场景是,Activity 中展示 Flutter 界面,Flutter 界面中嵌入 Android 视图,Android 视图中使用到输入法。如果该 Activity 在后台被回收,下次用户打开时,Activity 需要重新创建,这时 Android 视图中使用到输入法还是从旧的 Context 里拿到的,这可能会有问题。


PresentationContext

PresentationContext是一个自定义的ContextWrapper,它包装了基础的Context对象,并添加了一些额外的功能。这个类的主要作用是替换系统的窗口管理器(WindowManager)为我们自定义的实例。

以下是PresentationContext类的主要功能:

  1. 替换窗口管理器:在getSystemService(String name)方法中,如果请求的系统服务是窗口服务(WINDOW_SERVICE),那么这个方法将返回我们自定义的窗口管理器,而不是系统的窗口管理器。这是通过调用getWindowManager()方法实现的。

  2. 处理警告对话框:如果我们检测到是AlertDialog的构造函数在请求窗口管理器,那么我们将返回应用程序窗口的窗口管理器,而不是我们自定义的窗口管理器。这是因为警告对话框显示在整个应用程序之上,不应该被限制在虚拟显示器上。这个功能是通过isCalledFromAlertDialog()方法和getSystemService(String name)方法中的条件判断实现的。

  3. 缓存窗口管理器:在getWindowManager()方法中,我们会缓存我们自定义的窗口管理器,以便在后续的请求中重复使用。这是通过检查windowManager是否为null,如果为null,则调用windowManagerHandler.getWindowManager()来获取窗口管理器实现的。


SingleViewPresentation是一个内部类,它继承自Presentation类。Presentation类是Android中用于在不同的显示设备上显示不同内容的类。在这个情况下,SingleViewPresentation被用来在虚拟显示设备上显示一个单一的视图。

以下是SingleViewPresentation的核心部分:

  1. 构造函数:它接收一系列参数,包括上下文、显示设备、视图工厂、无障碍事件代理、视图ID、创建参数和焦点变化监听器。这些参数用于初始化SingleViewPresentation的实例。

  2. onCreate方法:当Presentation创建时,这个方法会被调用。在这个方法中,它首先调用父类的onCreate方法,然后创建一个新的PresentationState实例,并将其赋值给presentationState属性。然后,它使用视图工厂创建一个新的PlatformView实例,并将其赋值给presentationState.platformView。最后,它将PlatformView的视图添加到Presentation的内容视图中。

  3. onStop方法:当Presentation停止时,这个方法会被调用。在这个方法中,它首先调用PlatformViewdispose方法,然后调用父类的onStop方法。

  4. onStart方法:当Presentation开始时,这个方法会被调用。在这个方法中,它首先调用父类的onStart方法,然后调用PlatformViewonFlutterViewAttached方法。

  5. onDisplayRemoved方法:当显示设备被移除时,这个方法会被调用。在这个方法中,它首先调用PlatformViewdispose方法,然后调用父类的onDisplayRemoved方法。

  6. detachFromFlutterEngine方法:这个方法用于从Flutter引擎中分离PlatformView。在这个方法中,它首先调用PlatformViewonFlutterViewDetached方法,然后将presentationState设置为null

网络资源


本文作者:Maeiee

本文链接:Flutter SingleViewPresentation

版权声明:如无特别声明,本文即为原创文章,版权归 Maeiee 所有,未经允许不得转载!


喜欢我文章的朋友请随缘打赏,鼓励我创作更多更好的作品!